package edu.hawaii.halealohacli.command;

import java.util.Calendar;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;

/**
 * Periodically reports the current power usage compared to previously-set baseline goal.
 * <p>
 * Usage: monitor-goal [tower | lounge] [goal] [interval]
 * <p>
 * This command prints out a timestamp, the current power being consumed 
 * by the [tower | lounge], and whether or not the lounge is meeting its 
 * power conservation goal.
 * <p> 
 * [goal] is an integer between 0 and 99. It defines the percentage reduction from
 * the baseline for this [tower | lounge] at this point in time. For example, 
 * assume the user has previously defined the baseline power for Ilima-A as 100 kW
 * for the time interval between 6am and 7am, and the current time is 6:30am. 
 * If the goal is set as 5, then Ilima-A's current power must be 5% less than its 
 * baseline in order to make the goal. At the current time, that means that
 * Ilima-A should be using 95 kW or less of power in order to make its goal.
 * <p> 
 * It is an error if the monitor-goal command is invoked without a prior 
 * set-baseline command for that [tower | lounge].
 * <p>
 * [interval] is an optional argument and defaults to 10 seconds.  This command
 * will repeat it output every [interval] seconds. Pressing Enter stops this 
 * monitoring process and returns the user to the command loop.
 * 
 * @author Jeffrey M. Beck
 * @author Zach Tomaszewski
 */
public class MonitorGoal extends RepeatedCommand implements Command {

  /** How frequently this command executes if an interval is not specified. */
  public static final int DEFAULT_INTERVAL = 10;  
  
  private static final String HEADER_FORMAT = 
      "%-10s | %-10s | %-13s | %-10s | %-10s | %-10s%n";
  private static final String ROW_FORMAT = 
      " %-10tT   %-10.2f   %-13.2f   %-10s   %-10.2f   %-10s";
  
  private WattDepotClient client = null; 
  private String sourceName = null;
  private int goal = -1;
  private double[] baseline;
  private int interval = DEFAULT_INTERVAL;
  private String error = null;  //if set, then then command is invalid


  /**
   * Expects an argument array containing the name of the source to find 
   * current power for, the reduction goal as a percentage of the baseline
   * for that source, and (optionally) a check-frequency interval in seconds.
   * 
   * @param args A string array containing the two (or three) arguments.
   * @param client The WattDepot client to collect this data from.
   */
  @Override
  public void initialize(String[] args, WattDepotClient client) {
    this.client = client;       
    if (args != null && (args.length >= 2 && args.length <= 3)) {
      //valid number of args, so parse all args (error-checking comes later)
      this.sourceName = args[0];
      if (args.length >= 2) {
        try {
          this.goal = Integer.parseInt(args[1]);
        }
        catch (NumberFormatException e) { 
          this.goal = -1;
        }        
      }
      if (args.length == 3) {
        try {
          this.interval = Integer.parseInt(args[2]);
        }
        catch (NumberFormatException e) { 
          this.interval = -1;
        }
      }
      
      //now error-checking
      if (this.client == null || !this.client.isHealthy()) {
        error = "Invalid WattDepot client";
      }
      else if (this.sourceName == null) { 
        error = "Source name not given.";
      }
      else if (this.goal < 0 || this.goal > 99) {
        this.error = "Goal must be an integer between 0 and 99.";
      }
      else if (this.interval <= 0) {
        error = "Interval must be a positive integer.";
      }
      else if (!SetBaseline.isSet(sourceName)) { //NOPMD 
        //NOPMD: it makes more sense to handle the one valid case in the else
        error = "Baseline data not yet set by using the 'set-baseline " + 
            sourceName + "' command."; 
      }
      else {
        //valid
        baseline = SetBaseline.getBaseline(sourceName);
      }
    }
    else {
      error = "Invalid arguments for " + getCommandName() + ".\n" + getCommandUsage();      
    }  
  }

  /**
   * Prints the returned value from getResults to System.out.
   */
  @Override
  public void execute() {
    if (error == null) { 
      //command is valid, so print header and start repeating for each data line
      System.out.println("Querying " + this.sourceName + 
          " for current power every " + this.interval + " seconds. ");
      System.out.println("(Press Enter to stop.)");
      System.out.println();
      
      System.out.printf(HEADER_FORMAT, "Timestamp", "Power (kW)", 
          "Baseline (kW)", "Base Hour",  "Goal (kW)", "Goal met?");
      this.execute(this.interval);
    }
    else {
      System.out.println(error); 
    }
  }

  @Override
  public void doTask() {
    System.out.println(this.getResults());    
  }

  /**
   * Queries the WattDepot server for current power consumption, returning a 
   * single string containing the latest data timestamp, power consumption,
   * goal consumption, and whether this goal was met.
   * <p>
   * If there was an error, will return an error message instead. 
   * 
   * @return The message to display to the user.
   */
  public String getResults() {
    if (error != null) {
      return error;
    }
    
    //reused CurrentPower object to get current power data
    CurrentPower cp = new CurrentPower();
    cp.initialize(new String[]{sourceName}, client);
    try {
      //get timestamp and current power from server
      CurrentPower.Data data = cp.getCurrentPower();
      double current = data.getPower() / 1000; //in kW
      
      //get current hour from server timestamp (0 to 23)
      int hour = data.getTimestamp().get(Calendar.HOUR_OF_DAY);
      String hourRange = String.format("%02d-%02d", hour, hour + 1);
      
      //compute goal
      double goalLevel = baseline[hour] * (100 - goal) / 100;
      
      return String.format(ROW_FORMAT, 
          data.getTimestamp(), 
          current,
          baseline[hour],
          hourRange,
          goalLevel,
          (current <= goalLevel) ? "yes" : "no"
      );
      
    }
    catch (ResourceNotFoundException e) {
      return sourceName + " is not a valid source name.";
    }
    catch (Exception e) {
      return "Sensor data could not be retrieved for " + sourceName;
    }
  }

  @Override
  public String getCommandName() {
    return "monitor-goal";
  }

  @Override
  public String getCommandUsage() {
    return getCommandName()
        + " [tower | lounge] [goal] [interval]\n" 
        + "Prints a timestamp, current power being consumed," 
        + "and if the goal is acheived every [interval] seconds."
        + "\nGoal defines the percentage reduction from the baseline."
        + "The valid range is 0 to 99."
        + "\nThe interval (seconds) defines how often to check and print the results."
        + "\nIf no interval (seconds) is specified, 10 is the default."
        + "\nPress Enter to stop monitoring and return to the command loop.\n\n";
  }

}
